package top.wilsonlv.jaguar.oauth2.security.component;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import top.wilsonlv.jaguar.commons.data.encryption.util.EncryptionUtil;
import top.wilsonlv.jaguar.oauth2.security.Oauth2SecurityConstant;
import top.wilsonlv.jaguar.oauth2.security.properties.JaguarOauth2SecurityProperties;
import top.wilsonlv.jaguar.security.component.JaguarAccessDeniedHandler;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Map;

import static top.wilsonlv.jaguar.oauth2.security.Oauth2SecurityConstant.*;

/**
 * @author lvws
 * @since 2022/1/7
 */
@Component
@RequiredArgsConstructor
public class FeignServerSecurityFilter extends OncePerRequestFilter {

    private final RedisTemplate<String, Serializable> redisTemplate;

    private final JaguarAccessDeniedHandler jaguarAccessDeniedHandler;

    private final JaguarOauth2SecurityProperties jaguarOauth2SecurityProperties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
            throws IOException, ServletException {
        if (!request.getRequestURI().startsWith(Oauth2SecurityConstant.FEIGN_URL_PREFIX)) {
            filterChain.doFilter(request, response);
            return;
        }

        String serverId = request.getHeader(OPENFEIGN_PARAMETER_PRE + PARAMETER_SERVER_ID);
        if (!StringUtils.hasText(serverId)) {
            jaguarAccessDeniedHandler.handle(request, response, new AccessDeniedException("请提供serverId"));
            return;
        }

        String publicKey;
        if (jaguarOauth2SecurityProperties.getAuthServerId().equals(serverId)) {
            publicKey = jaguarOauth2SecurityProperties.getAuthServerPublicKey();
        } else {
            BoundValueOperations<String, Serializable> operations =
                    redisTemplate.boundValueOps(RESOURCE_SERVER_CACHE_KEY_PREFIX + serverId);
            Serializable serializable = operations.get();
            if (serializable == null) {
                jaguarAccessDeniedHandler.handle(request, response, new AccessDeniedException("serverId未注册"));
                return;
            }
            publicKey = (String) serializable;
        }

        Long timestamp = Long.parseLong(request.getHeader(OPENFEIGN_PARAMETER_PRE + PARAMETER_TIMESTAMP));
        String nonce = request.getHeader(OPENFEIGN_PARAMETER_PRE + PARAMETER_NONCE);
        String sign = request.getHeader(OPENFEIGN_PARAMETER_PRE + PARAMETER_SIGN);

        Map<String, String[]> parameters = request.getParameterMap();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            if (key.startsWith(OPENFEIGN_PARAMETER_PRE) && !key.equals(OPENFEIGN_PARAMETER_PRE + PARAMETER_SIGN)) {
                parameters.put(key, new String[]{request.getHeader(key)});
            }
        }

        String error = EncryptionUtil.checkRequestBySign(timestamp, nonce, sign, parameters, publicKey);
        if (StringUtils.hasText(error)) {
            jaguarAccessDeniedHandler.handle(request, response, new AccessDeniedException(error));
        } else {
            filterChain.doFilter(request, response);
        }
    }
}
